﻿// License: The MIT License (MIT) Copyright (c) 2011..2012 Barend Gehrels

// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;

namespace GrooveSharp
{
    /// <summary>
    /// This GUI class implements the library (treeview / folder structure to add tracks from)
    /// </summary>
    public partial class MusicCollection : UserControl
    {

        private class TreeTag
        {
            // The ID of the tag, negative for files/folders, positive for things from the database
            public int Id 
            { get; set; }


            public string FileOrFolderName { get; set; }

            public override string ToString() { return FileOrFolderName; }
        }

        public MusicCollection()
        {
            InitializeComponent();
        }


        // Cookbook methods to implement an event
        // http://www.codeproject.com/KB/WPF/WPF_CustomerControl.aspx#
        public static readonly RoutedEvent addTrackEvent =
                  EventManager.RegisterRoutedEvent("AddTrack", RoutingStrategy.Bubble,
                    typeof(RoutedEventHandler), typeof(MusicCollection));

        public event RoutedEventHandler AddTrack
        {
            add { AddHandler(addTrackEvent, value); }
            remove { RemoveHandler(addTrackEvent, value); }
        }

        /// <remarks>
        /// Is an IList for performance reasons (.Count, recursive behaviour)
        /// </remarks>
        private void AddDBListToTree(ItemCollection items, IList<CollectionItem> dbItems, int index)
        {
            CollectionItem previous = new CollectionItem();
            foreach (CollectionItem dbItem in dbItems)
            {
                if (index >= dbItem.Items.Count)
                {
                    return;
                }

                if (previous.Items.Count == 0 || previous.Items[index] != dbItem.Items[index])
                {
                    // Construct a new one, either with complete info (if on lowest level), or empty to add subnodes later
                    bool hasSubNodes = index < dbItem.Items.Count - 1;
                    TreeViewItem item = new TreeViewItem()
                        {
                            Header = dbItem.Items[index],
                            HorizontalAlignment= HorizontalAlignment.Left,
                            VerticalAlignment = VerticalAlignment.Bottom,
                            Tag = hasSubNodes
                                ? new TreeTag()
                                : new TreeTag()
                                    {
                                        Id = dbItem.TrackId,
                                        FileOrFolderName = dbItem.FileName
                                    }
                        };
                    items.Add(item);

                    AddDBListToTree(item.Items,
                        (from p in dbItems where p.Items.Count > 0 && p.Items[index] == dbItem.Items[index] select p).ToList(),
                        index + 1);
                }
                previous = dbItem;
            }
        }

        private void AddFolderToTree(ItemCollection items, String folderName, int currentId)
        {
            bool hasSub = false;
            foreach (String subFolder in Directory.GetDirectories(folderName))
            {
                TreeViewItem item = new TreeViewItem()
                {
                    Header = System.IO.Path.GetFileName(subFolder),
                    Tag = new TreeTag()
                    {
                        FileOrFolderName = subFolder
                    }
                };
                items.Add(item);
                AddFolderToTree(item.Items, subFolder, currentId);
                hasSub = true;
            }
            if (!hasSub)
            {
                var files = new List<string>(Directory.GetFiles(folderName, "*.mp3"))
                                    .Concat(Directory.GetFiles(folderName, "*.mp4"))
                                    .Concat(Directory.GetFiles(folderName, "*.m4a"))
                                    ;

                foreach (String file in files)
                {
                    TreeViewItem item = new TreeViewItem()
                    {
                        Header = System.IO.Path.GetFileNameWithoutExtension(file),
                        Tag = new TreeTag()
                        {
                            Id = --currentId,
                            FileOrFolderName = file
                        }
                    };
                    items.Add(item);
                }
            }
        }

        private void AddDQueryToTree(string sql)
        {
            App.Log.Info("Start filling tree from database");
            AddDBListToTree(treeViewCollection.Items, Db.Connection().Select<CollectionItem>(sql).ToList(), 0);
            App.Log.Info("End filling tree from database");
        }

        private void AddFromDB()
        {
            AddDQueryToTree(@"
select a.artist_name
    ,(select max(track_year) from tracks tt where tt.album_id=t.album_id) || ' - ' || m.album_title as album_year_title
    ,t.track_number || ' - ' || t.track_title as track_number_title
    ,t.track_id,p.path_folder,t.folder_name,t.file_name
from tracks t
    join albums m on t.album_id=m.album_id
    join artists a on a.artist_id=t.artist_id
    join paths p on p.path_id=t.path_id
where p.is_active = 1 and (t.is_deleted is null or t.is_deleted != 1)
order by artist_sortname,album_year_title,t.disc_number,t.track_number
                ");
        }
        private void AddFromYAT()
        {
            AddDQueryToTree(@"
select t.track_year,a.artist_name,m.album_title
    ,t.track_number || ' - ' || t.track_title as track_number_title
    ,t.track_id,p.path_folder,t.folder_name,t.file_name
from tracks t
    join albums m on t.album_id=m.album_id
    join artists a on a.artist_id=t.artist_id
    join paths p on p.path_id=t.path_id
where p.is_active = 1 and (t.is_deleted is null or t.is_deleted != 1)
order by t.track_year,artist_sortname,album_title,t.disc_number,t.track_number
                ");
        }

        private void AddDefaultFolderToTree()
        {
            string folderName = @"c:\Users\" + Environment.UserName + @"\Music";
#if DEBUG
            folderName = @"c:\temp\music\";
#endif
            AddFolderToTree(treeViewCollection.Items, folderName, 0);
        }

        public void LoadCollectionIfEmpty()
        {
            if (treeViewCollection.Items.IsEmpty)
            {
                AddFromDB();
                radioArtistAlbumTrack.IsChecked = true;

                if (treeViewCollection.Items.IsEmpty)
                {
                    radioFolder.IsChecked = true;
                    AddDefaultFolderToTree();
                    radioFolder.Visibility = Visibility.Visible;
                }
                else
                {
                    radioArtistAlbumTrack.IsEnabled = true;
                    radioYearAlbumTrack.IsEnabled = true;
                    radioFolder.Visibility = Visibility.Collapsed;
                }
            }
        }

        private void addButton_Click(object sender, RoutedEventArgs e)
        {
            if (treeViewCollection.SelectedItem != null)
            {
                RaiseEvent(new RoutedEventArgs(addTrackEvent));
            }
        }

        private IList<TrackInfo> SelectedTracks(TreeViewItem item)
        {
            var result = new List<TrackInfo>();
            if (item.Items.Count > 0)
            {
                // Recursively add all subnodes (either from file-system, or DB based)
                foreach (TreeViewItem subItem in item.Items)
                {
                    result.AddRange(SelectedTracks(subItem));
                }
            }
            else if (item.Tag is TreeTag)
            {
                // Just add the selected item, can be from filesystem or DB based
                TreeTag tag = item.Tag as TreeTag;
                result.Add(TrackInfoHelper.GetTrackInfo(tag.Id, tag.FileOrFolderName));
            }
            return result;
        }


        public IList<TrackInfo> SelectedTracks()
        {
            return treeViewCollection.SelectedItem != null && treeViewCollection.SelectedItem is TreeViewItem
                ? SelectedTracks(treeViewCollection.SelectedItem as TreeViewItem)
                : new List<TrackInfo>()
                ;
        }

        bool m_scanning = false;
        public void Scan()
        {
            textBlockScanErrorMessage.Visibility = Visibility.Hidden;

            // NOTE: we use it more than once so assure we have a list.
            var paths = Db.Connection().Select<GroovePath>("select * from paths where is_active=1").ToList();

            int folderCount = 0;
            foreach (var path in paths)
            {
                try
                {
                    folderCount += Directory.GetDirectories(path.PathName).Count();
                }
                catch (Exception ex)
                {
                    App.Log.ErrorFormat("Exception in counting folder {0}: {1}", path.PathName, ex.Message);

                    // Report to user:
                    textBlockScanErrorMessage.Text = String.Format("Error in scanning folder {0}: {1}", path.PathName, ex.Message);
                    textBlockScanErrorMessage.Visibility = Visibility.Visible;
                }
            }
            App.Log.InfoFormat("SCAN PLAN {0} folders in {1} paths", folderCount, paths.Count());

            textBlockScanProgress.Visibility = Visibility.Visible;
            progressFolderScan.Visibility = Visibility.Visible;
            progressFolderScan.Maximum = 100; // This is in percent.

            BackgroundWorker worker = new BackgroundWorker();
            worker.WorkerReportsProgress = true;
            worker.ProgressChanged += delegate(object s, ProgressChangedEventArgs args)
            {
                textBlockScanProgress.Text = args.UserState.ToString();
                progressFolderScan.Value = args.ProgressPercentage;

                App.Log.DebugFormat("SCAN - Progress Changed, {0} of {1}", args.ProgressPercentage, progressFolderScan.Maximum);
            };
            worker.DoWork += delegate(object s, DoWorkEventArgs args)
            {
                if (!m_scanning)
                {
                    try
                    {
                        App.Log.Info("SCAN START");
                        m_scanning = true;
                        var scanner = new FolderScanner();

                        int index = 0;
                        foreach (var path in paths)
                        {
                            if (System.IO.Directory.Exists(path.PathName))
                            {
                                scanner.Scan(path, folderCount, ref index, worker);
                            }
                        }
                    }
                    finally
                    {
                        m_scanning = false;
                    }
                }
            };
            worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
            {
                App.Log.Info("SCAN COMPLETED");

                radioArtistAlbumTrack.IsEnabled = true;
                radioYearAlbumTrack.IsEnabled = true;

                textBlockScanProgress.Text = "Finished";
                progressFolderScan.Value = progressFolderScan.Maximum;

                // And hide them:
                if (!m_scanning)
                {
                    textBlockScanProgress.Visibility = Visibility.Hidden;
                    progressFolderScan.Visibility = Visibility.Hidden;
                }

                // Refresh if the collection does not have the focus (note that focus is hardly determinable here, SelectedItem works wel)
                if (treeViewCollection.SelectedItem == null)
                {
                    this.Refresh();
                }
                //else
                //{
                //    textBlockScanErrorMessage.Text = String.Format("No refresh because item has focus {0}", (treeViewCollection.SelectedItem as TreeViewItem).Tag.ToString());
                //    textBlockScanErrorMessage.Visibility = Visibility.Visible;
                //}
            };

            worker.RunWorkerAsync();
        }


        private bool IsChecked(RadioButton r)
        {
            return r.IsChecked.HasValue && (bool) r.IsChecked.Value;
        }

        public void Refresh()
        {
            treeViewCollection.Items.Clear();
            if (IsChecked(radioFolder))
            {
                AddDefaultFolderToTree();
            }
            else if (IsChecked(radioArtistAlbumTrack))
            {
                AddFromDB();
            }
            else if (IsChecked(radioYearAlbumTrack))
            {
                AddFromYAT();
            }
        }

        private void refreshButton_Click(object sender, RoutedEventArgs e)
        {
            Refresh();
        }

        private void treeViewCollection_MouseRightButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (e.Source is TreeViewItem)
            {
                TreeViewItem item = e.Source as TreeViewItem;
                TreeTag tag = item.Tag as TreeTag;
                if (tag != null && tag.FileOrFolderName != null)
                {
                    item.IsSelected = true;
                    info.Select(tag.Id, tag.FileOrFolderName);
                    popLink.IsOpen = true;
                }
            }
        }

        private void popLink_Closed(object sender, EventArgs e)
        {
            info.Select(0, "");
        }

    }

}
